﻿using System;
using System.Web;
using System.IO;
using System.Text;
using System.IO.Compression;

namespace Svnhost.Web
{
    /// <summary>
    /// Javascript文件GZip文件格式输出
    /// </summary>
    /// <example>
    /// 
    /// 映射节点配置
    /// 
    ///  &lt;HandlerItem MappingUrl="/GZip/js.ashx" IsPattern="false" TypeFullName="Svnhost.Web.GZipHandler, Svnhost.Common" /&gt;
    /// 
    /// URL: /Gzip/js.ashx/Ext/ext-all-debug.js
    /// 
    /// 表示输出/Ext/ext-all-debug.js文件的GZip格式，如浏览器客户端支持。
    /// </example>
    public class GZipHandler : IHttpHandler
    {
        /// <summary>
        /// Detects the byte order mark of a file and returns
        /// an appropriate encoding for the file.
        /// </summary>
        /// <param name="srcFile"></param>
        /// <returns></returns>
        public static Encoding GetFileEncoding(string srcFile)
        {
            // *** Use Default of Encoding.Default (Ansi CodePage)
            Encoding enc = Encoding.Default;

            // *** Detect byte order mark if any - otherwise assume default
            byte[] buffer = new byte[5];
            FileStream file = new FileStream(srcFile, FileMode.Open);
            file.Read(buffer, 0, 5);
            file.Close();

            if (buffer[0] == 0xef && buffer[1] == 0xbb && buffer[2] == 0xbf)
                enc = Encoding.UTF8;
            else if (buffer[0] == 0xfe && buffer[1] == 0xff)
                enc = Encoding.Unicode;
            else if (buffer[0] == 0 && buffer[1] == 0 && buffer[2] == 0xfe && buffer[3] == 0xff)
                enc = Encoding.UTF32;
            else if (buffer[0] == 0x2b && buffer[1] == 0x2f && buffer[2] == 0x76)
                enc = Encoding.UTF7;

            return enc;
        }


        /// <summary>
        /// 通过实现 <see cref="T:System.Web.IHttpHandler"/> 接口的自定义 HttpHandler 启用 HTTP Web 请求的处理。
        /// </summary>
        /// <param name="context"><see cref="T:System.Web.HttpContext"/> 对象，它提供对用于为 HTTP 请求提供服务的内部服务器对象（如 Request、Response、Session 和 Server）的引用。</param>
        public void ProcessRequest(HttpContext context)
        {
            HttpContext Context = HttpContext.Current;
            HttpRequest Request = Context.Request;
            HttpResponse Response = Context.Response;
            if (Request.PathInfo.Length < 2)
            {
                Response.Write("请指定请求文件的路径信息，形如：*.ashx/myBigSizeJs.js");
                Response.End();
            }

            string AcceptEncoding = Request.Headers["Accept-Encoding"];
            // *** Start by checking whether GZip is supported by client
            bool UseGZip = (!string.IsNullOrEmpty(AcceptEncoding) && AcceptEncoding.ToLower().IndexOf("gzip") > -1);

            // *** Create a cachekey and check whether it exists
            string CacheKey = (Request.PathInfo + "+" + UseGZip.ToString()).ToUpper();
            byte[] Output = Context.Cache[CacheKey] as byte[];
            if (Output != null)
            {
                // *** Yup - read cache and send to client
                SendOutput(Output, UseGZip);
                return;
            }

            // *** 加载脚本文件
            //string scriptFilePath = context.Server.MapPath(Request["src"]);
            string scriptFilePath = context.Server.MapPath(Request.PathInfo);
            System.Text.Encoding fileEncoding = GetFileEncoding(scriptFilePath);

            string scriptContent = new StreamReader(scriptFilePath, fileEncoding).ReadToEnd();
            // *** Now we're ready to create out output

            // *** Optimize the script by removing comment lines and stripping spaces
            //if (!Context.IsDebuggingEnabled)
            //    scriptContent = JavaScriptMinifier.JsMin(scriptContent);

            // *** Don't GZip unless at least 8k
            if (UseGZip && scriptContent.Length > 6000)
            {
                Output = GZipMemory(fileEncoding, scriptContent);
            }
            else
            {
                Output = fileEncoding.GetBytes(scriptContent);
                UseGZip = false;
            }

            // *** Add into the cache with one day
            //Context.Cache.Add(CacheKey, Output, null, DateTime.UtcNow.AddDays(1), TimeSpan.Zero, System.Web.Caching.CacheItemPriority.High, null);

            //修改为根据所依赖文件更新缓存
            context.Cache.Insert(CacheKey, Output, new System.Web.Caching.CacheDependency(scriptFilePath));

            // *** Write out to Response object with appropriate Client Cache settings
            this.SendOutput(Output, UseGZip);
        }

        /// <summary>
        /// Sends the output to the client using appropriate cache settings.
        /// Content should be already encoded and ready to be sent as binary.
        /// </summary>
        /// <param name="Output">The output.</param>
        /// <param name="UseGZip">if set to <c>true</c> [use Gzip].</param>
        private void SendOutput(byte[] Output, bool UseGZip)
        {

            HttpResponse Response = HttpContext.Current.Response;
            Response.ContentType = "application/x-javascript";
            Response.Charset = "utf-8";

            if (UseGZip)
                Response.AppendHeader("Content-Encoding", "gzip");
            if (!HttpContext.Current.IsDebuggingEnabled)
            {
                Response.ExpiresAbsolute = DateTime.UtcNow.AddYears(1);
                Response.Cache.SetLastModified(DateTime.UtcNow);
                Response.Cache.SetCacheability(HttpCacheability.Public);
            }
            Response.BinaryWrite(Output);
            Response.End();

        }

        /// <summary>
        /// Takes a binary input buffer and GZip encodes the input
        /// </summary>
        /// <param name="Buffer">The buffer.</param>
        /// <returns></returns>
        public static byte[] GZipMemory(byte[] Buffer)
        {

            MemoryStream ms = new MemoryStream();
            GZipStream GZip = new GZipStream(ms, CompressionMode.Compress);
            GZip.Write(Buffer, 0, Buffer.Length);
            GZip.Close();
            byte[] Result = ms.ToArray();
            ms.Close();
            return Result;
        }

        /// <summary>
        /// Gets the zip memory.
        /// </summary>
        /// <param name="enc">The encoding</param>
        /// <param name="Input">The input.</param>
        /// <returns></returns>
        public static byte[] GZipMemory(Encoding enc, string Input)
        {
            return GZipMemory(enc.GetBytes(Input));
        }

        /// <summary>
        /// 获取一个值，该值指示其他请求是否可以使用 <see cref="T:System.Web.IHttpHandler"/> 实例。
        /// </summary>
        /// <value></value>
        /// <returns>
        /// 如果 <see cref="T:System.Web.IHttpHandler"/> 实例可再次使用，则为 true；否则为 false。
        /// </returns>
        public bool IsReusable
        {
            get
            {
                return false;
            }
        }

    }
}